/**
 * 枚举类
 * 在某些情况下，一个类的对象是有限而且固定的，比如季节类，它只有4个对象；
 * 再比如行星类，目前只有8个对象。
 * 这种实例有限而且固定的类，在Java里被称为枚举类。
 * <p>
 * Java 5新增了一个enum关键字（它与class、interface关键字的地位相同），用以定义枚举类。
 * 正如前面看到的，枚举类是一种特殊的类，它一样可以有自己的成员变量、方法，可以实现一个或者多个接口，也可以定义自己的构造器。
 * 一个Java源文件中最多只能定义一个public访问权限的枚举类，且该Java源文件也必须和该枚举类的类名相同。
 * <p>
 * 但枚举类终究不是普通类，它与普通类有如下简单区别。
 * ➢ 枚举类可以实现一个或多个接口，使用enum定义的枚举类默认继承了java.lang.Enum类，而不是默认继承Object类，因此枚举类不能显式继承其他父类。
 * 其中java.lang.Enum类实现了java.lang.Serializable和java.lang.Comparable两个接口。
 * ➢ 使用enum定义、非抽象的枚举类默认会使用final修饰。
 * ➢ 枚举类的构造器只能使用private访问控制符，如果省略了构造器的访问控制符，则默认使用private修饰；
 * 如果强制指定访问控制符，则只能指定private修饰符。由于枚举类的所有构造器都是private的，
 * 而子类构造器总要调用父类构造器一次，因此枚举类不能派生子类。
 * ➢ 枚举类的所有实例必须在枚举类的第一行显式列出，否则这个枚举类永远都不能产生实例。
 * 列出这些实例时，系统会自动添加public static final修饰，无须程序员显式添加。
 * <p>
 * 枚举类默认提供了一个values()方法，该方法可以很方便地遍历所有的枚举值。
 */

enum SeasonEnum {
    // 在第一行列出4个枚举实例
    SPRING, SUMMER, FALL, WINTER;
}

class SeasonEnumTest {
    public void judge(SeasonEnum s) {
        // 当switch控制表达式使用枚举类型时，后面case表达式中的值直接使用枚举值的名字，无须添加枚举类作为限定。
        switch (s) {
            case SPRING:
                System.out.println("春暖花开");
                break;
            case SUMMER:
                System.out.println("夏日炎炎");
                break;
            case FALL:
                System.out.println("秋高气爽");
                break;
            case WINTER:
                System.out.println("冬日雪飘");
                break;
        }
    }

    public static void test() {
        // 枚举类默认有一个values()方法，返回该枚举类的所有实例
        for (SeasonEnum s : SeasonEnum.values()) {
            System.out.println(s);
        }
        // 使用枚举实例时，可以通过EnumClass.variable形式来访问
        new SeasonEnumTest().judge(SeasonEnum.SPRING);
    }
}

/**
 * 前面已经介绍过，所有的枚举类都继承了java.lang.Enum类，所以枚举类可以直接使用java.lang.Enum类中所包含的方法。
 * java.lang.Enum类中提供了如下几个方法。
 * ➢ int compareTo(E o)：该方法用于与指定枚举对象比较顺序，同一个枚举实例只能与相同类型的枚举实例进行比较。
 * 如果该枚举对象位于指定枚举对象之后，则返回正整数；如果该枚举对象位于指定枚举对象之前，则返回负整数，否则返回零。
 * ➢ String name()：返回此枚举实例的名称，这个名称就是定义枚举类时列出的所有枚举值之一。
 * 与此方法相比，大多数程序员应该优先考虑使用toString()方法，因为toString()方法返回更加用户友好的名称。
 * ➢ int ordinal()：返回枚举值在枚举类中的索引值（就是枚举值在枚举声明中的位置，第一个枚举值的索引值为零）。
 * ➢ String toString()：返回枚举常量的名称，与name方法相似，但toString()方法更常用。
 * ➢ public static<T extends Enum<T>> T valueOf(Class<T> enumType，String name)：这是一个静态方法，
 * 用于返回指定枚举类中指定名称的枚举值。名称必须与在该枚举类中声明枚举值时所用的标识符完全匹配，不允许使用额外的空白字符。
 */
class JavaLangEnumTest {
    public static void test() {
        System.out.println(SeasonEnum.SPRING.compareTo(SeasonEnum.WINTER));
        System.out.println(SeasonEnum.SPRING.name());
        System.out.println(SeasonEnum.SPRING.toString());
        System.out.println(SeasonEnum.SPRING.ordinal());
        System.out.println(SeasonEnum.valueOf("SPRING"));
        // 当程序使用System.out.println(s)语句来打印枚举值时，
        // 实际上输出的是该枚举值的toString()方法，也就是输出该枚举值的名字。
        System.out.println(SeasonEnum.SPRING);
    }
}

/**
 * 枚举类的成员变量、方法和构造器
 * 枚举类也是一种类，只是它是一种比较特殊的类，因此它一样可以定义成员变量、方法和构造器。
 * 下面程序将定义一个Gender枚举类，该枚举类里包含了一个name实例变量。
 */
enum Gender {
    MALE, FEMALE;
    // 定义一个public修饰的实例变量
    public String name;
}

/**
 * 上面的Gender枚举类里定义了一个名为name的实例变量，并且将它定义成一个public访问权限的。
 * 下面通过如下程序来使用该枚举类。
 */
class GenderTest {
    public static void test() {
        // 通过Enum的valueOf()方法来获取指定枚举类的枚举值
        Gender g = Enum.valueOf(Gender.class, "FEMALE");
        // 直接为枚举值的name实例变量赋值
        g.name = "女";
        // 直接访问枚举值的name实例变量
        System.out.println(g + "代表：" + g.name);
    }
}

/**
 * 上面程序把name设置成private，从而避免其他程序直接访问该name成员变量，
 */
enum Gender2 {
    // 此处的枚举值必须调用对应的构造器来创建
    MALE("男"), FEMALE("女");
    // 上面一行等同于如下两行
//    public static final Gender2 MALE = new Gender2("男");
//    public static final Gender2 FEMALE = new Gender2("女");

    private final String name;

    // 枚举类的构造器只能使用private修饰
    private Gender2(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}

/**
 * 从上面程序中可以看出，当为Gender枚举类创建了一个Gender(String name)构造器之后，列出枚举值就应该采用 MALE("男"), FEMALE("女"); 代码来完成。
 * 也就是说，在枚举类中列出枚举值时，实际上就是调用构造器创建枚举类对象，只是这里无须使用new关键字，也无须显式调用构造器。
 * <p>
 * 前面列出枚举值时无须传入参数，甚至无须使用括号，仅仅是因为前面的枚举类包含无参数的构造器。
 */

/**
 * 实现接口的枚举类
 * 枚举类也可以实现一个或多个接口。与普通类实现一个或多个接口完全一样，枚举类实现一个或多个接口时，也需要实现该接口所包含的方法。
 * 下面程序定义了一个GenderDesc接口。
 */
interface GenderDesc {
    void info();
}

enum Gender3 implements GenderDesc {
    MALE("男"), FEMALE("女");

    private final String name;

    private Gender3(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    @Override
    public void info() {
        System.out.println("这是一个用于定义性别的枚举类");
    }
}

/**
 * 如果由枚举类来实现接口里的方法，则每个枚举值在调用该方法时都有相同的行为方式（因为方法体完全一样）。
 * 如果需要每个枚举值在调用该方法时呈现出不同的行为方式，则可以让每个枚举值分别来实现该方法，
 * 每个枚举值提供不同的实现方式，从而让不同的枚举值调用该方法时具有不同的行为方式。
 * 在下面的Gender枚举类中，不同的枚举值对info()方法的实现各不相同
 */
enum Gender4 implements GenderDesc {
    /**
     * 花括号部分实际上就是一个类体部分，在这种情况下，当创建MALE、FEMALE枚举值时，
     * 并不是直接创建Gender枚举类的实例，而是相当于创建Gender的匿名子类的实例。
     * 因为括号部分实际上是匿名内部类的类体部分，所以这个部分的代码语法与前面介绍的匿名内部类语法大致相似，只是它依然是枚举类的匿名内部子类。
     */
    MALE("男") {
        @Override
        public void info() {
            System.out.println("这个枚举值代表男性");
        }
    },
    FEMALE("女") {
        @Override
        public void info() {
            System.out.println("这个枚举值代表女性");
        }
    };
    /**
     * 枚举类不是用final修饰了吗？怎么还能派生子类呢？
     * 并不是所有的枚举类都使用了final修饰！非抽象的枚举类才默认使用final修饰。
     * 对于一个抽象的枚举类而言——只要它包含了抽象方法，它就是抽象枚举类，系统会默认使用abstract修饰，而不是使用final修饰。
     */

    private final String name;

    private Gender4(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}

/**
 * 包含抽象方法的枚举类
 * 假设有一个Operation枚举类，它的4个枚举值PLUS，MINUS，TIMES，DIVIDE分别代表加、减、乘、除4种运算，
 * 该枚举类需要定义一个eval()方法来完成计算。
 * <p>
 * 从上面描述可以看出，Operation需要让PLUS、MINUS、TIMES、DIVIDE四个值对eval()方法各有不同的实现。
 * 此时可考虑为Operation枚举类定义一个eval()抽象方法，然后让4个枚举值分别为eval()提供不同的实现。
 * 例如如下代码。
 */
enum Operation {
    PLUS {
        @Override
        public double eval(double x, double y) {
            return x + y;
        }
    },
    MINUS {
        @Override
        public double eval(double x, double y) {
            return x - y;
        }
    },
    TIMES {
        @Override
        public double eval(double x, double y) {
            return x * y;
        }
    },
    DIVIDE {
        @Override
        public double eval(double x, double y) {
            return x / y;
        }
    };

    /**
     * 为枚举类定义一个抽象方法
     * 这个抽象方法由不同的枚举值提供不同的实现
     */
    public abstract double eval(double x, double y);

    public static void test() {
        System.out.println(Operation.PLUS.eval(3, 4));
        System.out.println(Operation.MINUS.eval(5, 4));
        System.out.println(Operation.TIMES.eval(5, 4));
        System.out.println(Operation.DIVIDE.eval(5, 4));
    }
}

/**
 * 枚举类里定义抽象方法时不能使用abstract关键字将枚举类定义成抽象类（因为系统自动会为它添加abstract关键字），
 * 但因为枚举类需要显式创建枚举值，而不是作为父类，所以定义每个枚举值时必须为抽象方法提供实现，否则将出现编译错误。
 */

public class EnumTest {
    public static void main(String[] args) {
        SeasonEnumTest.test();
        JavaLangEnumTest.test();
        GenderTest.test();
        Operation.test();
    }
}
